package com.rj.core.algorithm;

import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

/**
 * 对称加密算法枚举
 */
public enum SymmetricAlgorithm {
    /**
     * AES/CBC/PKCS5Padding
     */
    AES_CBC("AES/CBC/PKCS5Padding", 128),
    /**
     * AES/GCM/NoPadding
     */
    AES_GCM("AES/GCM/NoPadding", 128),
    /**
     * SM4/CBC/PKCS5Padding
     */
    SM4_CBC("SM4/CBC/PKCS5Padding", 128),
    /**
     * SM4/ECB/PKCS5Padding
     */
    SM4_ECB("SM4/ECB/PKCS5Padding", 128);

    public final String transformation;
    public final int keySize;
    public boolean hasIv;
    public int length;
    public String algorithm;

    SymmetricAlgorithm(String transformation, int keySize) {
        this.transformation = transformation;
        this.keySize = keySize;
        this.length = keySize / 8;
        this.algorithm = name().split("_")[0];
        this.hasIv = name().contains("_CBC") || name().contains("_GCM");
    }

    public byte[] generateKey() {
        SecureRandom random = new SecureRandom();
        byte[] key = new byte[length];
        random.nextBytes(key);
        return key;
    }

    public byte[] generateIv() {
        byte[] iv = new byte[length];
        new SecureRandom().nextBytes(iv);
        return iv;
    }

    /**
     * 加密解密核心函数
     *
     * @param data 数据
     * @param key  密钥
     * @param iv   初始向量
     */
    public byte[] crypt(String data, int mode, String key, String iv) {
        return crypt(data.getBytes(), mode, key.getBytes(), iv == null ? null : iv.getBytes());
    }

    /**
     * 加密解密核心函数
     *
     * @param data 数据
     * @param key  密钥
     * @param iv   初始向量
     */
    public byte[] crypt(byte[] data, int mode, byte[] key, byte[] iv) {
        try {
            Cipher cipher = Cipher.getInstance(transformation);
            SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
            if (iv != null && hasIv) {
                switch (this) {
                    case AES_GCM:
                        cipher.init(mode, keySpec, new GCMParameterSpec(keySize, iv));
                        break;
                    default:
                        cipher.init(mode, keySpec, new IvParameterSpec(iv));
                }

            } else {
                cipher.init(mode, keySpec);
            }

            byte[] resData = cipher.doFinal(data);
            return resData;

        } catch (Exception e) {
            throw new RuntimeException((mode == Cipher.ENCRYPT_MODE ? "加密失败: " : "解密失败: ") + e.getMessage());
        }
    }
}